/**
 * Project: topdt-jbpm
 * Copyright 2013  Top Digital Technology  Co. Ltd.
 * All right reserved.
 */
package com.topdt.jbpm.extension;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.jbpm.api.TaskService;
import org.jbpm.api.model.OpenExecution;
import org.jbpm.api.task.Assignable;
import org.jbpm.api.task.AssignmentHandler;
import org.jbpm.api.task.Participation;
import org.jbpm.api.task.Task;
import org.jbpm.pvm.internal.task.OpenTask;
import org.jbpm.pvm.internal.task.TaskImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.thx.common.util.ThreadLocalContext;
import com.topdt.jbpm.form.FormHandle;
import com.topdt.jbpm.identity.ActorCalculator;
import com.topdt.jbpm.identity.User;
import com.topdt.jbpm.model.Activity;
import com.topdt.jbpm.model.Actor;
import com.topdt.jbpm.model.AppHandle;
import com.topdt.jbpm.model.Application;
import com.topdt.jbpm.model.Todo;
import com.topdt.jbpm.model.Work;
import com.topdt.jbpm.service.Context;
import com.topdt.jbpm.service.JbpmService;
import com.topdt.jbpm.utils.ObjectUtils;

/**
 * 建立日期 : 2013-4-16 下午4:23:32<br>
 * 作者 : balance<br>
 * 模块 : <br>
 * 描述 : <br>
 * 修改历史: 序号 日期 修改人 修改原因 <br>
 * 1 <br>
 * 2 <br>
 */
public class TaskAssigner implements AssignmentHandler {

	private static final long serialVersionUID = 1L;

	private static Logger log = LoggerFactory.getLogger(TaskAssigner.class);

	private List<User> actors = null;

	private Assignable assignable;

	private OpenExecution execution;

	private Context context;

	private Task task;

	private JbpmService jbpmService;

	private TaskService taskService;

	private Activity activity;

	private Work work;

	private String activityName;

	private long taskBatch;// 任务批次

	private String todoMode;// 待办模式

	private Boolean isGoBack;

	public void assign(Assignable assignable, OpenExecution execution) throws Exception {

		this.assignable = assignable;
		this.execution = execution;
		this.isGoBack = (Boolean) execution.getVariable("isGoBack_");
		this.isGoBack = this.isGoBack == null ? false : isGoBack;
		this.task = (Task) assignable;

		this.context = (Context) execution.getVariable(Context.KEY);

		this.jbpmService = JbpmService.getJbpmService();
		this.taskService = jbpmService.getTaskService();
		this.activityName = task.getActivityName();

		this.work = jbpmService.getWorkDao().get(context.getWorkId());

		/**
		 * 情况：1、前面环节已经指定好的；（静态定义【目前仅支持组织机构体系】+动态指定）；2、流程属性定义；3、组织机构体系定义；
		 *
		 * 处理人情况：多人并发、多人顺序、多人抢占、单人
		 */
		this.activity = jbpmService.getActivityDao().getActivityById(context.getFlowId(), activityName);

		evaluateActors();

		assignTask();

		pocket();

		// 业务处理
		Application app = jbpmService.getAppDao().get(work.getAppId());
		AppHandle handle = app.getHandle();
		String javaClass = handle.getHandleJava();
		Object obj = ObjectUtils.getInstance(javaClass);
		if (obj != null) {
			FormHandle formHandle = (FormHandle) obj;
			formHandle.executeBusiness(app, work);
		}

	}

	/*
	 * 设置活动执行人员
	 */
	private void evaluateActors() {

		// 退回处理

		String assignerActivity = activity.getAssignerActivity();

		execution.removeVariable("isGoBack_");

		actors = null;

		if (isGoBack || StringUtils.isNotBlank(assignerActivity)) {// 已经由前面环节指定好人员，从变量中取出

			/**
			 * //TODO 下面这句在foreach环境下有问题，在foreach中，这个变量前一个分支设置的值会被后一个分支的值覆盖掉，
			 * 最好通过线程环境传值，若发现问题时，考虑改为线程绑定传值
			 */
			String assignees = (String) execution.getVariable(activityName + "_assginee");

			String foreachUser = (String) execution.getVariable("foreach_user");// 动态分支情况

			if (assignees == null && foreachUser == null) {// 这个就很奇怪了，正常不应该的
				// TODO 弥补方法
				log.error("怎么就没指定，去哪了!");
			} else {
				if (assignees != null) {
					execution.removeVariable(activityName + "_assginee");

					String[] ids = assignees.split("[,]");
					actors = new ArrayList<User>();
					for (String id : ids) {
						actors.add(new Actor(id, ""));
					}
					ActorCalculator.fillName(actors);
				} else if (foreachUser != null) {// 动态分支情况
					execution.removeVariable("foreach_user");
					actors = new ArrayList<User>();
					actors.add(new Actor(foreachUser, ""));
				}
			}
		} else {
			if (activity.isAbsoluteModel()) {
				actors = ActorCalculator.absoluteCalculate(activity);
			} else if (activity.isRelativeModel()) {
				String doActor = (String) ThreadLocalContext.get("_currentActor");// 当前提交任务放入线程环境中
				ThreadLocalContext.remove("_currentActor");
				actors = ActorCalculator.relativeCalculate(activity, context, doActor);
			}
		}
	}

	private void assignTask() {
		if (actors == null || actors.size() == 0) {
			// TODO 没有处理人，怎么可能？严重异常！
			log.error("没有处理人?!");
		} else {

			taskBatch = System.currentTimeMillis();

			// 设置处理人
			if (!isGoBack) {
				context.addHistoryUser(activityName + taskBatch, actors);
			}
			execution.setVariable(Context.KEY, context);

			int actNUM = actors.size();// activity.getActorNUM();

			// 分配任务
			if (actNUM == 1) {// 就一个人，就好办了
				String actorId = actors.get(0).getId();
				assignable.setAssignee(actorId);
				todoMode = Todo.MODE_SINGLE;
				creatTodo(actorId, this.task.getId(), Todo.STATE_READY, 1);
			} else {// 多个人
				String doRule = activity.getDoRule();
				log.debug("==========多人执行规则为：" + doRule);
				if (Activity.DO_CONCURRENCE.equals(doRule)) {// 并行
					doConcurrence();
				} else if (Activity.DO_SEQUENCE.equals(doRule)) {// 顺序
					doSequence();
				} else if (Activity.DO_UNIQUE.equals(doRule)) {// 唯一（候选人）
					doUnique();
				}
			}
		}
	}

	/**
	 * 唯一
	 */
	private void doUnique() {
		log.debug("==========生成抢占任务============");
		int seqNUM = 1;
		for (User actor : actors) {
			this.assignable.addCandidateUser(actor.getId());
			creatTodo(actor.getId(), task.getId(), Todo.STATE_COMPETITIVE, seqNUM++);
		}
	}

	/**
	 * 顺序
	 */
	private void doSequence() {
		log.debug("==========生成顺序任务============");

		for (int i = 0; i < actors.size(); i++) {
			User actor = actors.get(i);

			TaskImpl subTask = ((TaskImpl) task).createSubTask();
			String userId = actor.getId();

			subTask.setProgress(i);// TODO:利用任务进度字段来实现序号
			subTask.setName(task.getName() + "_" + actor.getId());
			subTask.setActivityName(task.getActivityName());
			subTask.setDescription(task.getId() + "的子任务");
			subTask.setExecution(execution);// TODO:设置上后，任务变量和流程变量才能共享，但不清楚是否会造成其他隐患

			if (i == 0) {// 第一个人直接分配任务
				subTask.setAssignee(userId);
				taskService.addTaskParticipatingUser(task.getId(), userId, Participation.CLIENT);

				// TODO 扩展任务数据
				creatTodo(userId, subTask.getId(), Todo.STATE_READY, 1);
			} else {// 暂时将任务执行人记录在参与人表中
				subTask.addParticipation(userId, null, Participation.VIEWER);
			}

		}

	}

	/**
	 * 并行
	 */
	private void doConcurrence() {

		log.debug("==========生成并行任务============");

		int seqNUM = 1;

		for (User actor : actors) {
			TaskImpl subTask = (TaskImpl) ((OpenTask) task).createSubTask();
			subTask.setAssignee(actor.getId());
			subTask.setName(task.getName() + "_" + actor.getId());
			subTask.setActivityName(task.getActivityName());
			subTask.setDescription(task.getId() + "的子任务");
			subTask.setExecution(execution);// TODO:设置上后，任务变量和流程变量才能共享，但不清楚是否会造成其他隐患
			taskService.addTaskParticipatingUser(task.getId(), actor.getId(), Participation.CLIENT);

			log.debug("==========给" + actor.getId() + "生成并行任============");

			// TODO 扩展任务数据
			creatTodo(actor.getId(), subTask.getId(), Todo.STATE_READY, seqNUM++);

		}

	}

	private void creatTodo(String userId, String taskId, String todoState, int seqNUM) {
		Todo todo = new Todo();
		todo.setArriveTime(new Date());
		todo.setFromUserId(context.getCurrentActor());
		todo.setActivityName(this.activityName);
		todo.setTaskId(taskId);
		todo.setToUserId(userId);
		todo.setWorkId(context.getWorkId());
		todo.setState(todoState);
		todo.setBatch(taskBatch);
		todo.setTodoMode(todoMode);
		todo.setSeqNUM(seqNUM);
		todo.setAppName(context.getAppName());

		todo.setTodoTitle(work.getWorkTitle());
		todo.setPreActivityName(work.getCurActivityName());
		todo.setCompanyId(work.getCompanyId());

		jbpmService.getTodoDao().save(todo);

	}

	/**
	 * 将环节信息和人员信息缓存在线程变量里
	 */
	private void pocket() {
		Map<String, List<User>> next = null;

		Object obj = ThreadLocalContext.get("next_circs");
		if (obj == null) {
			next = new HashMap<String, List<User>>();
			ThreadLocalContext.set("next_circs", next);
		} else {
			next = (Map<String, List<User>>) obj;
		}
		if (actors != null && actors.size() == 0)
			actors = null;
		String pathName = this.execution.getName();// 并发分支标示
		if (pathName != null)
			next.put(pathName + "-" + activityName, actors);
		else
			next.put(activityName, actors);
	}

	public static void main(String[] args) {
		/*
		 * SendRecord[] messages = new SendRecord[3]; int index = 0; for (int
		 * i=0; i<3; i++) { SendRecord message = new SendRecord();
		 * message.setTodoId("userId+ "+i); messages[index] = message; index++;
		 * }
		 *
		 * for(SendRecord temp : messages) {
		 * System.out.println("---"+temp.getTodoId()); }
		 */
	}

}
